#include <san.h>

#include <chess.h>
#include <errors.h>
#include <position.h>
#include <movegen.h>
#include <game.h>

#include <string.h>


const char figures[] = "PNBRQK";
const int figure_pieces[] = {PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING};
const char promotion_figures[] = "nbrq";
const int promotion_pieces[] = {KNIGHT, BISHOP, ROOK, QUEEN};


char* move_to_string(move_t m, char* buf)
{
  assert(buf != 0);

  if (m == NULL_MOVE) {
    strcpy(buf, "null");
    return buf;
  }

  buf[0] = FILE(MOVE_FROM(m)) + 'a';
  buf[1] = RANK(MOVE_FROM(m)) + '1';
  buf[2] = FILE(MOVE_TO(m)) + 'a';
  buf[3] = RANK(MOVE_TO(m)) + '1';
  if (MOVE_PROMOTION(m) == NO_PROMOTION) {
    buf[4] = '\0';
  } else {
    char p = 'X';
    for (int i = 0; i < 4; i++)
      if (MOVE_PROMOTION(m) == promotion_pieces[i])
	p = promotion_figures[i];
    buf[4] = p;
    buf[5] = '\0';
  }

  return buf;
}


int move_from_string(const char* buf, move_t* m)
{
  assert(buf != 0);
  assert(m != 0);

  int len = strlen(buf);

  if (len < 4 || len > 5)
    return 0;

  if (buf[0] < 'a' || buf[0] > 'h' ||
      buf[1] < '1' || buf[1] > '8' ||
      buf[2] < 'a' || buf[2] > 'h' ||
      buf[3] < '1' || buf[3] > '8')
    return 0;

  int from = SQUARE(buf[0] - 'a', buf[1] - '1');
  int to = SQUARE(buf[2] - 'a', buf[3] - '1');
  *m = MAKE_MOVE(from, to);

  if (len > 4) {
    int p = NO_PROMOTION;
    for (int i = 0; i < 4; i++)
      if (buf[4] == promotion_figures[i])
	p = promotion_pieces[i];
    if (p == NO_PROMOTION)
      return 0;
    SET_MOVE_PROMOTION(*m, p);
  }

  return 1;
}


char* move_to_san_string(move_t m,
			 const struct position_t* pos,
			 char* buf)
{
  assert(pos != 0);
  assert(buf != 0);

  if (m == NULL_MOVE) {
    strcpy(buf, "null");
    return buf;
  }

  int from = MOVE_FROM(m);
  int to = MOVE_TO(m);
  int samefile = 0;
  int samerank = 0;
  int found = 0;
  int piece = position_get_square(pos, from);
  if (piece == EMPTY) {
    error_set(ERR_ILLEGAL_MOVE);
    return 0;
  }

  move_t moves[MAX_N_MOVES];
  int nmoves = movegen_legal_moves(pos, moves);

  for (int i = 0; i < nmoves; i++) {
    if (same_move(m, moves[i])) {
      m = moves[i];
      found = 1;
      continue;
    }

    int f = MOVE_FROM(moves[i]);
    int t = MOVE_TO(moves[i]);
    int q = position_get_square(pos, f);
    if (q == piece && to == t &&
	MOVE_PROMOTION(m) == MOVE_PROMOTION(moves[i])) {
      if (RANK(from) == RANK(f))
	samerank = 1;
      if (FILE(from) == FILE(f))
	samefile = 1;
      if (!samefile && !samerank)
	samerank = 1;  // use the file to disambiguate
    }
  }

  if (!found) {
    error_set(ERR_ILLEGAL_MOVE);
    return 0;
  }

  char* s = buf;
  if (m == MOVE_WCASTLE_SHORT || m == MOVE_BCASTLE_SHORT) {
    strcpy(buf, "O-O");
    s += 3;
  } else if (m == MOVE_WCASTLE_LONG || m == MOVE_BCASTLE_LONG) {
    strcpy(buf, "O-O-O");
    s += 5;
  } else {
    if (piece != PAWN) {
      for (int i = 1; i < 6; i++)
	if (figure_pieces[i] == piece)
	  *s++ = figures[i];
    }
    
    int capture = 0;
    if (position_get_square(pos, to) != EMPTY ||
	position_get_ep_square(pos) == to)
      capture = 1;
    
    if (samerank ||
	(piece == PAWN && FILE(from) != FILE(to)))
      *s++ = 'a' + FILE(from);
    if (samefile)
      *s++ = '1' + RANK(from);
    if (capture)
      *s++ = 'x';
    *s++ = 'a' + FILE(to);
    *s++ = '1' + RANK(to);
    int pr = MOVE_PROMOTION(m);
    if (pr != NO_PROMOTION) {
      *s++ = '=';
      if (pr == KNIGHT)
	*s++ = 'N';
      else if (pr == BISHOP)
	*s++ = 'B';
      else if (pr == ROOK)
	*s++ = 'R';
      else if (pr == QUEEN)
	*s++ = 'Q';
    }
  }

  // Check and chekmate.
  game_t game;
  if (game_init(&game)) {
    if (game_set_start_position(&game, pos)) {
      game_make_move(&game, m);
      if (game_is_checkmate(&game))
	*s++ = '#';
      else if (game_in_check(&game))
	*s++ = '+';
    }
    game_cleanup(&game);
  }

  *s = '\0';
  return buf;
}


int move_from_san_string(const char* buf,
			 const struct position_t* pos,
			 move_t* m)
{
  assert(pos != 0);
  assert(buf != 0);
  assert(m != 0);

  // 1) First try the simple algebraic notation
  if (move_from_string(buf, m))
    return 1;

  // 2) Castlings.
  int stm = position_get_stm(pos);
  if (strcmp(buf, "O-O") == 0 || 
      strcmp(buf, "O-O+") == 0 ||
      strcmp(buf, "O-O#") == 0) {
    if (stm == WHITE)
      *m = MOVE_WCASTLE_SHORT;
    else
      *m = MOVE_BCASTLE_SHORT;
    return 1;
  }

  if (strcmp(buf, "O-O-O") == 0 ||
      strcmp(buf, "O-O-O+") == 0 ||
      strcmp(buf, "O-O-O#") == 0) {
    if (stm == WHITE)
      *m = MOVE_WCASTLE_LONG;
    else
      *m = MOVE_BCASTLE_LONG;
    return 1;
  }

  // 3) Parse the string.
  int piece = PAWN;
  if (*buf == '\0') {
    error_set(ERR_ILL_FORMED_SAN);
    return 0;
  }
  for (int i = 0; i < 6; i++) {
    if (*buf == figures[i]) {
      piece = figure_pieces[i];
      ++buf;
      break;
    }
  }

  int from_rank = -1;
  int from_file = -1;
  int to_rank = -1;
  int to_file = -1;
  int capture = 0;

  for (;*buf != '\0' && strchr("=+#!?", *buf) == 0; buf++) {
    if (*buf == 'x') {
      if (capture) {
	error_set(ERR_ILL_FORMED_SAN);
	return 0;
      }
      capture = 1;
    } else if (*buf >= 'a' && *buf <= 'h') {
      if (to_file != -1 || to_rank != -1) {
	if (from_file != -1 || from_rank != -1) {
	  error_set(ERR_ILL_FORMED_SAN);
	  return 0;
	}
	from_file = to_file;
	from_rank = to_rank;
      }
      to_file = *buf - 'a';
    } else if (*buf >= '1' && *buf <= '8') {
      to_rank = *buf - '1';
    } else {
      error_set(ERR_ILL_FORMED_SAN);
      return 0;
    }
  }

  if (to_file == -1 || to_rank == -1) {
    error_set(ERR_ILL_FORMED_SAN);
    return 0;
  }

  int promotion = NO_PROMOTION;
  if (*buf == '=') {
    ++buf;
    if (*buf == '\0')
      return 0;
    for (int i = 1; i < 5; i++)
      if (*buf == figures[i])
	promotion = figure_pieces[i];
    ++buf;
    if (promotion == NO_PROMOTION) {
      error_set(ERR_ILL_FORMED_SAN);
      return 0;
    }
  }

  char* boh;
  while (*buf != '\0' && (boh = strchr(" +#!?", *buf)) != 0)
    ++buf;

  if (*buf != '\0') {
    error_set(ERR_ILL_FORMED_SAN);
    return 0;
  }

  // 4) Find a move matching with the data gathered.
  int found = 0;
  move_t moves[MAX_N_MOVES];
  int nmoves = movegen_legal_moves(pos, moves);
  for (int i = 0; i < nmoves; i++) {
    if (MOVE_TO(moves[i]) != SQUARE(to_file, to_rank))
      continue;
    if (from_file != -1 &&
	from_file != FILE(MOVE_FROM(moves[i])))
      continue;
    if (from_rank != -1 &&
	from_rank != RANK(MOVE_FROM(moves[i])))
      continue;
    int q = position_get_square(pos, MOVE_FROM(moves[i]));
    if (piece != q)
      continue;
    q = MOVE_PROMOTION(moves[i]);
    if (promotion != q &&
	!(q == QUEEN && promotion == NO_PROMOTION))
      continue;
    *m = moves[i];
    ++found;
  }

  if (found == 0) {
    error_set(ERR_ILL_FORMED_SAN);
    return 0;
  } else if (found > 1) {
    error_set(ERR_AMBIGUOUS_SAN);
    return 0;
  }
  return 1;
}


char* moves_to_san_string(const move_t* moves,
			  int nmoves,
			  const char* separator,
			  const struct game_t* game,
			  char* buf)
{
  assert(moves != 0);
  assert(nmoves >= 0);
  assert(separator != 0);
  assert(game != 0);
  assert(buf != 0);

  // 1) Initialize a copy of the game.
  game_t g;
  if (!game_init(&g))
    return 0;
  if (!game_copy(game, &g)) {
    game_cleanup(&g);
    return 0;
  }

  // 2) Convert and execute each move on the copy.
  char* s = buf;
  int i;
  for (i = 0; i < nmoves; i++) {
    // 2a) Add the separator.
    if (i > 0) {
      for (const char* c = separator; *c != '\0'; c++)
	*s++ = *c;
    }

    // 2b) Convert the move.
    if (!move_to_san_string(moves[i], game_cur_position(&g), s)) {
      game_cleanup(&g);
      return 0;
    }
    while (*s != '\0')
      ++s;

    // 2c) Execute the move.
    if (i != nmoves - 1) {
      if (!game_make_move(&g, moves[i])) {
	game_cleanup(&g);	
	return 0;
      }
    }
  }

  // 3) Cleanup and return.
  game_cleanup(&g);
  return buf;
}
